package com.nulldev.util.crypto.api2;

import java.nio.charset.Charset;
import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.LinkedList;

import com.nulldev.util.FinalBypass;
import com.nulldev.util.crypto.api2.encryptors.AES;
import com.nulldev.util.crypto.api2.encryptors.Blowfish;
import com.nulldev.util.crypto.api2.encryptors.LegacyXOR;
import com.nulldev.util.crypto.api2.encryptors.P2PC;
import com.nulldev.util.crypto.api2.encryptors.SafeXOR;
import com.nulldev.util.crypto.api2.encryptors.XOR;
import com.nulldev.util.crypto.api2.encryptors.ZIP;
import com.nulldev.util.crypto.api2.encryptors.tDES;
import com.nulldev.util.crypto.api3.encryptors.P2PE;
import com.nulldev.util.crypto.api3.encryptors.P2PE64;
import com.nulldev.util.crypto.api3.encryptors.P2PEC;
import com.nulldev.util.crypto.api3.encryptors.p2pe2.P2PE2;
import com.nulldev.util.crypto.api3.encryptors.p2pe2.P2PE2C;
import com.nulldev.util.crypto.api3.encryptors.unsupported.P2PX;
import com.nulldev.util.crypto.api3.encryptors.unsupported.P2PXM;
import com.nulldev.util.crypto.api3.encryptors.unsupported.P2PXl;
import com.nulldev.util.logging.Logger;
import com.nulldev.util.logging.LoggerFactory;
import com.nulldev.util.other.ArrayUtils;
import com.nulldev.util.string.StringsUtil;
import com.nulldev.util.uri.URIUtil;

@Deprecated
public class EncryptionUtil {

	private static final Logger log = LoggerFactory.getLogger();
	public static final LinkedList<Cryptable> ENCRYPTORS = new LinkedList<Cryptable>();
	private static final Default DEF = new Default();
	
	static {
		if (ENCRYPTORS.isEmpty()) {
			ENCRYPTORS.add(new AES());
			ENCRYPTORS.add(new LegacyXOR());
			ENCRYPTORS.add(new XOR());
			ENCRYPTORS.add(new P2PC());
			ENCRYPTORS.add(new Blowfish());
			ENCRYPTORS.add(new SafeXOR());
			ENCRYPTORS.add(new ZIP());
			ENCRYPTORS.add(new tDES());
			ENCRYPTORS.add(new P2PX());
			ENCRYPTORS.add(new P2PXl());
			ENCRYPTORS.add(new P2PXM());
			ENCRYPTORS.add(new P2PE());
			ENCRYPTORS.add(new P2PE64());
			ENCRYPTORS.add(new P2PEC());
			ENCRYPTORS.add(new P2PE2());
			ENCRYPTORS.add(new P2PE2C());
			ENCRYPTORS.add(DEF);
		}
		log.warn("EncryptionUtil will be remade soon, you should consider encryptAPI a.k.a EncryptAPI.TYPE_V4.");
	}
	
	/**
	 * Offical Encryptors/Decryptors in nullUtil. <br>
	 * Deprecated encryptors shouldn't be used, they are usually a experimental one or are unsafe.
	 * @author null
	 *
	 */
	@Deprecated
	public static enum Types {
		
		AES("AES", "AES encryption. Experimental."), 
		LEGACY_XOR("Legacy.XOR", "Port of XOR from CryptoUtil."),
		XOR("XOR", "Simple XOR."),
		Safe_XOR("XOR.safe", "Safe iteration of XOR."),
		Blowfish("Blowfish", "Blowfish cipher."),
		P2PC("p2pC", "First generation of the P2P cipher."),
		@Deprecated
		P2PX("p2pX", "Second generation of the P2P cipher."),
		@Deprecated
		P2PXL("p2pX.l", "Better formatted edition of P2PX. WARNING: HIGH RAM USAGE ABOVE 2 iterations."),
		@Deprecated
		P2PXM("p2pX.M", "Improved P2PX, by that it iterates 2 times to provide better protection."),
		@Deprecated
		/**
		 * Unsafe, can be cracked easily.
		 * <br> Suffers from an invalid password hasher.
		 */
		P2PE("p2pE", "Basically fixed P2PX,XM,XL."),
		P2PE2("p2pE.v2", "P2PE II, safer and more durable, at a cost, data size really. ['Hello world!' becomes a 8K+ string.]"),
		P2PE2_COMPRESS("p2pE.v2.compress", "P2PE II, safer and more durable, now with compression, data size really. ['Hello world!' becomes a 8k string.]"),
		@Deprecated
		/**
		 * Unsafe, can be cracked easily.
		 * <br> Suffers from an invalid password hasher.
		 */
		P2PE_BASE64("p2pE.b64", "P2PE but delievered in a Base64 format."),
		@Deprecated
		/**
		 * Unsafe, can be cracked easily.
		 * <br> Suffers from an invalid password hasher.
		 */
		P2PE_COMPRESS("p2pE.compress", "P2PE but compressed to save space."),
		ZIP("zip", "Compresses a string. Experimental."),
		tDES("tDES", "Triple layered DES. Experimental."),
		CAESAR("caesar", "Simple ASCII letter shift, Password length = characters to shift."),
		DEFAULT("default", "Default. No really that's it.");
		
		private final String id, desc;
		private Types(final String id) {
			this.id = id;
			this.desc = "No description.";
		}
		
		private Types(final String id, final String desc) {
			this.id = id;
			this.desc = desc;
		}
		
		public String getID() {
			return this.id;
		}
		
		public String getDesc() {
			return this.desc;
		}
		
		@Override
		public String toString() {
			return this.id;
		}
		
		public static void debugAll() {
			final Types[] t = values();
			for (final Types tx : t) {
				System.out.println(tx.getID() + " | " + tx.getDesc());
			}
		}
		
		public static Types parse(final String id) {
			for (final Types type : values()) {
				if (type.getID().equals(id)) {
					return type;
				}
			}
			return null;
		}
	}

	@Deprecated
	public static Cryptable byID(final String id) {
		for (final Cryptable crypt : ENCRYPTORS) {
			if (crypt.id().equals(id)) {
				log.warn("byID() is a Deprecated method. You should use byType(Types). [Called by " + Thread.currentThread().getStackTrace()[2].getClassName() + "]");
				return crypt;
			}
		}
		return DEF;
	}
	
	public static Cryptable byType(final Types encryptor) {
		for (final Cryptable crypt : ENCRYPTORS) {
			if (crypt.id().equals(encryptor.getID())) {
				return crypt;
			}
		}
		return DEF;
	}
	
	public static Types getType(final String id) {
		for (final Types type : Types.values()) {
			if (type.getID().equals(id)) {
				return type;
			}
		}
		return Types.DEFAULT;
	}
	
	public static void debug_Ciphers() {
		for (final Provider provider: Security.getProviders()) {
			System.out.println("Provider: " + provider.getName());
			for (final String key : provider.stringPropertyNames()) {
				System.out.println("	- Key: " + key);
			}
		}
	}
	
	public static String[] getCiphers() {
		final ArrayList<String> chps = new ArrayList<String>();
		for (final Provider provider: Security.getProviders()) {
			for (final String key : provider.stringPropertyNames()) {
				final String pro = key.split(" ")[0];
				if (!URIUtil.isValid(pro)) {
					chps.add(pro);
				}
			}
		}
		return ArrayUtils.arrayListToArray(chps);
	}
	
	private static class Default extends Cryptable {

		public Default() {
			super("default");
		}

		@Override
		public String encrypt(final String object, final String pass) {
			return object;
		}

		@Override
		public String decrypt(final String object, final String pass) {
			return object;
		}

		@Override
		public String encrypt(String object, String pass, Charset charset) {
			return object;
		}

		@Override
		public String decrypt(String object, String pass, Charset charset) {
			return object;
		}
		
	}

	@Deprecated
	public static String p2pXMultiencrypt(final String object, final String pass, final int iterations) {
		final FinalBypass<String> data = new FinalBypass<String>(StringsUtil.mul(object, pass));
		for (int i = 0; i < iterations; i++) {
			data.setObject(StringsUtil.mul(data.getObject(), pass));
		}
		return data.getObject();
	}
	
	@Deprecated
	public static String p2pXMultidecrypt(final String object, final String pass, final int iterations) {
		final FinalBypass<String> data = new FinalBypass<String>(object);
		for (int i = iterations; i > 0; i--) {
			data.setObject(StringsUtil.unmul(data.getObject(), pass));
		}
		data.setObject(StringsUtil.unmul(data.getObject(), pass));
		return data.getObject();
	}
}
